
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;
using Derm.OpenGL;

namespace Derm.Render
{
	/// <summary>
	/// Depth test render state.
	/// </summary>
	public sealed class DepthTestState : RenderState
	{
		#region Constructors
		
		/// <summary>
		/// Construct a default DepthTestState.
		/// </summary>
		public DepthTestState()
		{
			
		}

		/// <summary>
		/// Construct a DepthTestState.
		/// </summary>
		/// <param name="depthFunction">
		/// A <see cref="DepthFunction"/> that specify the test function to apply.
		/// </param>
		public DepthTestState(DepthFunction depthFunction)
		{
			mFunction = depthFunction;
		}

		/// <summary>
		/// Construct the current DepthTestState.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> defining this RenderState.
		/// </param>
		public DepthTestState(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.DebugEnabled) {
				if (ctx.IsCurrent() == false)
					throw new ArgumentException("not current", "ctx");
			}

			int depthFunction;

			// Depth function
			Gl.Get(Gl.DEPTH_FUNC, out depthFunction);
			RenderException.DebugCheckErrors();

			// Store current state
			mFunction = (DepthFunction)depthFunction;
		}

		#endregion
		
		#region Depth Test State Definition
		
		/// <summary>
		/// The depth comparison test function.
		/// </summary>
		public enum DepthFunction
		{
			/// <summary>
			/// Depth function disabled.
			/// </summary>
			Disabled,
			/// <summary>
			/// Depth test never passes.
			/// </summary>
			Never = Gl.NEVER,
			/// <summary>
			/// Depth test passes if the incoming depth value is less than the stored depth value.
			/// </summary>
			/// <remarks>This value is the system default.</remarks>
			Less = Gl.LESS,
			/// <summary>
			/// Depth test passes if the incoming depth value is equal to the stored depth value.
			/// </summary>
			Equal = Gl.EQUAL,
			/// <summary>
			/// Depth test passes if the incoming depth value is not equal to the stored depth value.
			/// </summary>
			NotEqual = Gl.NOTEQUAL,
			/// <summary>
			/// Depth test passes if the incoming depth value is less than or equal to the stored depth value.
			/// </summary>
			LessEqual = Gl.LEQUAL,
			/// <summary>
			/// Depth test passes if the incoming depth value is greater than the stored depth value.
			/// </summary>
			Greater = Gl.GREATER,
			/// <summary>
			/// Depth test passes if the incoming depth value is greater than or equal to the stored depth value.
			/// </summary>
			GreaterEqual = Gl.GEQUAL,
			/// <summary>
			/// Depth test always passes.
			/// </summary>
			Always = Gl.ALWAYS
		}
		
		/// <summary>
		/// Depth test function property.
		/// </summary>
		/// <remarks>The default value is <see cref="DepthFunction.Less"/>.</remarks>
		public DepthFunction Function
		{
			get { return (mFunction); }
		}

		/// <summary>
		/// Depth test function.
		/// </summary>
		private readonly DepthFunction mFunction = DepthFunction.Less;
		
		#endregion

		#region Default State

		/// <summary>
		/// The system default state for DepthTestState.
		/// </summary>
		public static DepthTestState DefaultState { get { return (new DepthTestState()); } }

		#endregion
		
		#region RenderState Overrides

		/// <summary>
		/// The identifier for the blend state.
		/// </summary>
		public static string StateId = "GL.Depth";

		/// <summary>
		/// The identifier of this RenderState.
		/// </summary>
		public override string Id { get { return (StateId); } }

		/// <summary>
		/// Flag indicating whether the state is context-bound.
		/// </summary>
		/// <remarks>
		/// It returns always true.
		/// </remarks>
		public override bool IsContextBound { get { return (true); } }

		/// <summary>
		/// Apply this depth test render state.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> which has defined the shader program <paramref name="sProgram"/>.
		/// </param>
		/// <param name="sProgram">
		/// The <see cref="ShaderProgram"/> which has the state set.
		/// </param>
		public override void ApplyState(RenderContext ctx, ShaderProgram sProgram)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.DebugEnabled) {
				if (ctx.IsCurrent() == false)
					throw new ArgumentException("not current", "ctx");
			}

			if (Function != DepthFunction.Disabled) {
				// Enable depth test
				Gl.Enable(Gl.DEPTH_TEST);
				RenderException.DebugCheckErrors();
				// Specify depth function
				Gl.DepthFunc((int) Function);
				RenderException.DebugCheckErrors();
			} else {
				// Disable depth test
				Gl.Disable(Gl.DEPTH_TEST);
				RenderException.DebugCheckErrors();
			}
		}

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">
		/// A <see cref="RenderState"/> to compare to this RenderState.
		/// </param>
		/// <returns>
		/// It returns true if the current object is equal to <paramref name="other"/>.
		/// </returns>
		/// <remarks>
		/// <para>
		/// This method test only whether <paramref name="other"/> type equals to this type.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="other"/> is null.
		/// </exception>
		public override bool Equals(IRenderState other)
		{
			if (base.Equals(other) == false)
				return (false);
			Debug.Assert(other is DepthTestState);

			DepthTestState otherState = (DepthTestState) other;

			if (otherState.Function != Function)
				return (false);

			return (true);
		}

		/// <summary>
		/// Performs a deep copy of this <see cref="IRenderState"/>.
		/// </summary>
		/// <returns>
		/// It returns the equivalent of this <see cref="IRenderState"/>, but all objects referenced
		/// are not referred by both instances.
		/// </returns>
		public override IRenderState Copy()
		{
			return (new DepthTestState(Function));
		}

		#endregion
	}
}
